/* eslint-disable */
export function startMin() {
  var Stats = function() {
    function e(e) {
      return n.appendChild(e.dom), e;
    }
    function t(e) {
      for (var t = 0; t < n.children.length; t++)
        n.children[t].style.display = t === e ? 'block' : 'none';
      l = e;
    }
    var l = 0,
      n = document.createElement('div');
    (n.style.cssText = 'cursor:pointer;opacity:0.9'),
      n.addEventListener(
        'click',
        function(e) {
          e.preventDefault(), t(++l % n.children.length);
        },
        !1
      );
    var a = (performance || Date).now(),
      i = a,
      o = 0,
      r = e(new Stats.Panel('FPS', '#0ff', '#002')),
      f = e(new Stats.Panel('MS', '#0f0', '#020'));
    if (self.performance && self.performance.memory)
      var c = e(new Stats.Panel('MB', '#f08', '#201'));
    return (
      t(0),
      {
        REVISION: 16,
        domElement: n,
        addPanel: e,
        showPanel: t,
        setMode: t,
        begin: function() {
          a = (performance || Date).now();
        },
        end: function() {
          o++;
          var e = (performance || Date).now();
          if (
            (f.update(e - a, 200),
            e > i + 1e3 &&
              (r.update((1e3 * o) / (e - i), 100), (i = e), (o = 0), c))
          ) {
            var t = performance.memory;
            c.update(t.usedJSHeapSize / 1048576, t.jsHeapSizeLimit / 1048576);
          }
          return e;
        },
        update: function() {
          a = this.end();
        },
      }
    );
  };
  (Stats.Panel = function(e, t, l) {
    var n = 1 / 0,
      a = 0,
      i = Math.round,
      o = i(window.devicePixelRatio || 1),
      r = 80 * o,
      f = 48 * o,
      c = 3 * o,
      d = 2 * o,
      s = 3 * o,
      p = 15 * o,
      u = 74 * o,
      m = 30 * o,
      h = document.createElement('canvas');
    (h.width = r), (h.height = f), (h.style.cssText = 'width:80px;height:48px');
    var S = h.getContext('2d');
    return (
      (S.font = 'bold ' + 9 * o + 'px Helvetica,Arial,sans-serif'),
      (S.textBaseline = 'top'),
      (S.fillStyle = l),
      S.fillRect(0, 0, r, f),
      (S.fillStyle = t),
      S.fillText(e, c, d),
      S.fillRect(s, p, u, m),
      (S.fillStyle = l),
      (S.globalAlpha = 0.9),
      S.fillRect(s, p, u, m),
      {
        dom: h,
        update: function(f, v) {
          (n = Math.min(n, f)),
            (a = Math.max(a, f)),
            (S.fillStyle = l),
            (S.globalAlpha = 1),
            S.fillRect(0, 0, r, p),
            (S.fillStyle = t),
            S.fillText(i(f) + ' ' + e + ' (' + i(n) + '-' + i(a) + ')', c, d),
            S.drawImage(h, s + o, p, u - o, m, s, p, u - o, m),
            S.fillRect(s + u - o, p, o, m),
            (S.fillStyle = l),
            (S.globalAlpha = 0.9),
            S.fillRect(s + u - o, p, o, i((1 - f / v) * m));
        },
      }
    );
  }),
    'object' == typeof module && (module.exports = Stats);
}

export class Shader {
  constructor(holder, options = {}) {
    options = Object.assign(
      {
        antialias: false,
        depthTest: false,
        mousemove: false,
        autosize: true,
        side: 'front',
        vertex: `
        precision highp float;

        attribute vec4 a_position;
        attribute vec4 a_color;

        uniform float u_time;
        uniform vec2 u_resolution;
        uniform vec2 u_mousemove;
        uniform mat4 u_projection;

        varying vec4 v_color;

        void main() {

          gl_Position = u_projection * a_position;
          gl_PointSize = (10.0 / gl_Position.w) * 100.0;

          v_color = a_color;

        }`,
        fragment: `
        precision highp float;

        uniform sampler2D u_texture;
        uniform int u_hasTexture;

        varying vec4 v_color;

        void main() {

          if ( u_hasTexture == 1 ) {

            gl_FragColor = v_color * texture2D(u_texture, gl_PointCoord);

          } else {

            gl_FragColor = v_color;

          }

        }`,
        uniforms: {},
        buffers: {},
        camera: {},
        texture: null,
        onUpdate: () => {},
        onResize: () => {},
      },
      options
    );

    const uniforms = Object.assign(
      {
        time: { type: 'float', value: 0 },
        hasTexture: { type: 'int', value: 0 },
        resolution: { type: 'vec2', value: [0, 0] },
        mousemove: { type: 'vec2', value: [0, 0] },
        projection: {
          type: 'mat4',
          value: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
        },
      },
      options.uniforms
    );

    const buffers = Object.assign(
      {
        position: { size: 3, data: [] },
        color: { size: 4, data: [] },
      },
      options.buffers
    );

    const camera = Object.assign(
      {
        fov: 60,
        near: 1,
        far: 10000,
        aspect: 1,
        z: 100,
        perspective: true,
      },
      options.camera
    );

    const canvas = document.createElement('canvas');
    const gl = canvas.getContext('webgl', { antialias: options.antialias });

    if (!gl) return false;

    this.count = 0;
    this.gl = gl;
    this.canvas = canvas;
    this.camera = camera;
    this.holder = holder;
    this.onUpdate = options.onUpdate;
    this.onResize = options.onResize;
    this.data = {};

    holder.appendChild(canvas);

    this.createProgram(options.vertex, options.fragment);

    this.createBuffers(buffers);
    this.createUniforms(uniforms);

    this.updateBuffers();
    this.updateUniforms();

    this.createTexture(options.texture);

    gl.enable(gl.BLEND);
    gl.enable(gl.CULL_FACE);
    gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
    gl[options.depthTest ? 'enable' : 'disable'](gl.DEPTH_TEST);

    if (options.autosize)
      window.addEventListener('resize', e => this.resize(e), false);
    if (options.mousemove)
      window.addEventListener('mousemove', e => this.mousemove(e), false);

    this.resize();

    this.update = this.update.bind(this);
    this.time = { start: performance.now(), old: performance.now() };
    this.update();
  }

  mousemove(e) {
    let x = (e.pageX / this.width) * 2 - 1;
    let y = (e.pageY / this.height) * 2 - 1;

    this.uniforms.mousemove = [x, y];
  }

  resize(e) {
    const holder = this.holder;
    const canvas = this.canvas;
    const gl = this.gl;

    const width = (this.width = holder.offsetWidth);
    const height = (this.height = holder.offsetHeight);
    const aspect = (this.aspect = width / height);
    const dpi = devicePixelRatio;

    canvas.width = width * dpi;
    canvas.height = height * dpi;
    canvas.style.width = width + 'px';
    canvas.style.height = height + 'px';

    gl.viewport(0, 0, width * dpi, height * dpi);
    gl.clearColor(0, 0, 0, 0);

    this.uniforms.resolution = [width, height];
    this.uniforms.projection = this.setProjection(aspect);

    this.onResize(width, height, dpi);
  }

  setProjection(aspect) {
    const camera = this.camera;

    if (camera.perspective) {
      camera.aspect = aspect;

      const fovRad = camera.fov * (Math.PI / 180);
      const f = Math.tan(Math.PI * 0.5 - 0.5 * fovRad);
      const rangeInv = 1.0 / (camera.near - camera.far);

      const matrix = [
        f / camera.aspect,
        0,
        0,
        0,
        0,
        f,
        0,
        0,
        0,
        0,
        (camera.near + camera.far) * rangeInv,
        -1,
        0,
        0,
        camera.near * camera.far * rangeInv * 2,
        0,
      ];

      matrix[14] += camera.z;
      matrix[15] += camera.z;

      return matrix;
    } else {
      return [
        2 / this.width,
        0,
        0,
        0,
        0,
        -2 / this.height,
        0,
        0,
        0,
        0,
        1,
        0,
        -1,
        1,
        0,
        1,
      ];
    }
  }

  createShader(type, source) {
    const gl = this.gl;
    const shader = gl.createShader(type);

    gl.shaderSource(shader, source);
    gl.compileShader(shader);

    if (gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
      return shader;
    } else {
      console.log(gl.getShaderInfoLog(shader));
      gl.deleteShader(shader);
    }
  }

  createProgram(vertex, fragment) {
    const gl = this.gl;

    const vertexShader = this.createShader(gl.VERTEX_SHADER, vertex);
    const fragmentShader = this.createShader(gl.FRAGMENT_SHADER, fragment);

    const program = gl.createProgram();

    gl.attachShader(program, vertexShader);
    gl.attachShader(program, fragmentShader);
    gl.linkProgram(program);

    if (gl.getProgramParameter(program, gl.LINK_STATUS)) {
      gl.useProgram(program);
      this.program = program;
    } else {
      console.log(gl.getProgramInfoLog(program));
      gl.deleteProgram(program);
    }
  }

  createUniforms(data) {
    const gl = this.gl;
    const uniforms = (this.data.uniforms = data);
    const values = (this.uniforms = {});

    Object.keys(uniforms).forEach(name => {
      const uniform = uniforms[name];

      uniform.location = gl.getUniformLocation(this.program, 'u_' + name);

      Object.defineProperty(values, name, {
        set: value => {
          uniforms[name].value = value;
          this.setUniform(name, value);
        },
        get: () => uniforms[name].value,
      });
    });
  }

  setUniform(name, value) {
    const gl = this.gl;
    const uniform = this.data.uniforms[name];

    uniform.value = value;

    switch (uniform.type) {
      case 'int': {
        gl.uniform1i(uniform.location, value);
        break;
      }
      case 'float': {
        gl.uniform1f(uniform.location, value);
        break;
      }
      case 'vec2': {
        gl.uniform2f(uniform.location, ...value);
        break;
      }
      case 'vec3': {
        gl.uniform3f(uniform.location, ...value);
        break;
      }
      case 'vec4': {
        gl.uniform4f(uniform.location, ...value);
        break;
      }
      case 'mat2': {
        gl.uniformMatrix2fv(uniform.location, false, value);
        break;
      }
      case 'mat3': {
        gl.uniformMatrix3fv(uniform.location, false, value);
        break;
      }
      case 'mat4': {
        gl.uniformMatrix4fv(uniform.location, false, value);
        break;
      }
    }
  }

  updateUniforms() {
    const gl = this.gl;
    const uniforms = this.data.uniforms;

    Object.keys(uniforms).forEach(name => {
      const uniform = uniforms[name];

      this.uniforms[name] = uniform.value;
    });
  }

  createBuffers(data) {
    const gl = this.gl;
    const buffers = (this.data.buffers = data);
    const values = (this.buffers = {});

    Object.keys(buffers).forEach(name => {
      const buffer = buffers[name];

      buffer.buffer = this.createBuffer('a_' + name, buffer.size);

      Object.defineProperty(values, name, {
        set: data => {
          buffers[name].data = data;
          this.setBuffer(name, data);

          if (name == 'position') this.count = buffers.position.data.length / 3;
        },
        get: () => buffers[name].data,
      });
    });
  }

  createBuffer(name, size) {
    const gl = this.gl;
    const program = this.program;

    const index = gl.getAttribLocation(program, name);
    const buffer = gl.createBuffer();

    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
    gl.enableVertexAttribArray(index);
    gl.vertexAttribPointer(index, size, gl.FLOAT, false, 0, 0);

    return buffer;
  }

  setBuffer(name, data) {
    const gl = this.gl;
    const buffers = this.data.buffers;

    if (name == null && !gl.bindBuffer(gl.ARRAY_BUFFER, null)) return;

    gl.bindBuffer(gl.ARRAY_BUFFER, buffers[name].buffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
  }

  updateBuffers() {
    const gl = this.gl;
    const buffers = this.buffers;

    Object.keys(buffers).forEach(name => (buffers[name] = buffer.data));

    this.setBuffer(null);
  }

  createTexture(src) {
    const gl = this.gl;
    const texture = gl.createTexture();

    gl.bindTexture(gl.TEXTURE_2D, texture);
    gl.texImage2D(
      gl.TEXTURE_2D,
      0,
      gl.RGBA,
      1,
      1,
      0,
      gl.RGBA,
      gl.UNSIGNED_BYTE,
      new Uint8Array([0, 0, 0, 0])
    );

    this.texture = texture;

    if (src) {
      this.uniforms.hasTexture = 1;
      this.loadTexture(src);
    }
  }

  loadTexture(src) {
    const gl = this.gl;
    const texture = this.texture;

    const textureImage = new Image();

    textureImage.onload = () => {
      gl.bindTexture(gl.TEXTURE_2D, texture);

      gl.texImage2D(
        gl.TEXTURE_2D,
        0,
        gl.RGBA,
        gl.RGBA,
        gl.UNSIGNED_BYTE,
        textureImage
      );

      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);

      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
    };

    textureImage.src = src;
  }

  update() {
    const gl = this.gl;

    const now = performance.now();
    const elapsed = (now - this.time.start) / 5000;
    const delta = now - this.time.old;
    this.time.old = now;

    this.uniforms.time = elapsed;

    if (this.count > 0) {
      gl.clear(gl.COLORBUFFERBIT);
      gl.drawArrays(gl.POINTS, 0, this.count);
    }

    this.onUpdate(delta);

    requestAnimationFrame(this.update);
  }
}

const snowflake =
  '';

export function initObj(holder) {
  const count = parseInt(holder.getAttribute('count'));
  let wind = {
    current: 0,
    force: 0.1,
    target: 0.1,
    min: 0.1,
    max: 0.1,
    easing: 0.005,
  };

  new Shader(holder, {
    depthTest: false,
    texture: snowflake,
    uniforms: {
      worldSize: { type: 'vec3', value: [0, 0, 0] },
      gravity: { type: 'float', value: 100 },
      wind: { type: 'float', value: 0 },
    },
    buffers: {
      size: { size: 1, data: [] },
      rotation: { size: 3, data: [] },
      speed: { size: 3, data: [] },
    },
    vertex: `
          precision highp float;
      
          attribute vec4 a_position;
          attribute vec4 a_color;
          attribute vec3 a_rotation;
          attribute vec3 a_speed;
          attribute float a_size;
      
          uniform float u_time;
          uniform vec2 u_mousemove;
          uniform vec2 u_resolution;
          uniform mat4 u_projection;
          uniform vec3 u_worldSize;
          uniform float u_gravity;
          uniform float u_wind;
      
          varying vec4 v_color;
          varying float v_rotation;
      
          void main() {
      
            v_color = a_color;
            v_rotation = a_rotation.x + u_time * a_rotation.y;
      
            vec3 pos = a_position.xyz;
      
            pos.x = mod(pos.x + u_time + u_wind * a_speed.x, u_worldSize.x * 2.0) - u_worldSize.x;
            pos.y = mod(pos.y - u_time * a_speed.y * u_gravity, u_worldSize.y * 2.0) - u_worldSize.y;
      
            pos.x += sin(u_time * a_speed.z) * a_rotation.z;
            pos.z += cos(u_time * a_speed.z) * a_rotation.z;
      
            gl_Position = u_projection * vec4( pos.xyz, a_position.w );
            gl_PointSize = ( a_size / gl_Position.w ) * 100.0;
      
          }`,
    fragment: `
          precision highp float;
      
          uniform sampler2D u_texture;
      
          varying vec4 v_color;
          varying float v_rotation;
      
          void main() {
      
            vec2 rotated = vec2(
              cos(v_rotation) * (gl_PointCoord.x - 0.5) + sin(v_rotation) * (gl_PointCoord.y - 0.5) + 0.5,
              cos(v_rotation) * (gl_PointCoord.y - 0.5) - sin(v_rotation) * (gl_PointCoord.x - 0.5) + 0.5
            );
      
            vec4 snowflake = texture2D(u_texture, rotated);
      
            gl_FragColor = vec4(snowflake.rgb, snowflake.a * v_color.a);
      
          }`,
    onResize(w, h, dpi) {
      const position = [],
        color = [],
        size = [],
        rotation = [],
        speed = [];

      // z in range from -80 to 80, camera distance is 100
      // max height at z of -80 is 110
      const height = 110;
      const width = (w / h) * height;
      const depth = 80;

      Array.from({ length: (w / h) * count }, snowflake => {
        position.push(
          -width + Math.random() * width * 2,
          -height + Math.random() * height * 2,
          Math.random() * depth * 2
        );

        speed.push(
          // 0, 0, 0 )
          1 + Math.random(),
          1 + Math.random(),
          Math.random() * 10
        ); // x, y, sinusoid

        rotation.push(
          Math.random() * 2 * Math.PI,
          Math.random() * 20,
          Math.random() * 10
        ); // angle, speed, sinusoid

        color.push(1, 1, 1, 0.1 + Math.random() * 0.2);

        size.push(5 * Math.random() * 5 * ((h * dpi) / 1000));
      });

      this.uniforms.worldSize = [width, height, depth];

      this.buffers.position = position;
      this.buffers.color = color;
      this.buffers.rotation = rotation;
      this.buffers.size = size;
      this.buffers.speed = speed;
    },
    onUpdate(delta) {
      wind.force += (wind.target - wind.force) * wind.easing;
      wind.current += wind.force * (delta * 0.2);
      this.uniforms.wind = wind.current;

      if (Math.random() > 0.995) {
        wind.target =
          (wind.min + Math.random() * (wind.max - wind.min)) *
          (Math.random() > 0.5 ? -1 : 1);
        console.log(wind.target);
      }
    },
  });
}
